package controllers;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.TypedQuery;

import models.DeliveryOption;
import models.DietInformation;
import models.Drink;
import models.PaymentMethod;
import models.Product;
import models.PurchasedOrder;
import models.Salad;
import models.ShippingInformation;
import models.ShoppingCart;
import models.ShoppingCartLine;
import models.SportProfile;
import models.User;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;

import play.db.jpa.GenericModel;
import play.db.jpa.GenericModel.JPAQuery;
import play.libs.Mail;
import play.libs.OAuth2;
import play.libs.WS;
import play.mvc.Before;
import play.mvc.Controller;
import utils.PasswordUtility;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

public class Application extends Controller {

	private static String SHOPPING_CART_ID = "ShoppingCartId";
	private static String USER_ID = "UserId";
	public static String LAST_PAGE_VISITED = "lastPage";

	/**
	 * Renders the index
	 */
	public static void index() {
		List<Product> products = Product.findAll();
		render(products);
	}

	/**
	 * Renders the index
	 */
	public static void index2() {
		List<Product> products = getProducts();
		String textOption = getTextOption();
		User user = getCurrentUser();
		ShoppingCart shoppingCart = getShoppingCart();
		render(products, textOption, user, shoppingCart);
	}

	private static String getTextOption(){
		String text;
		LastPageVisited option =  LastPageVisited.ORIGINALS;
		if (session.contains(LAST_PAGE_VISITED))
			option = (LastPageVisited)Enum.valueOf(LastPageVisited.class, session.get(LAST_PAGE_VISITED));

		if (option == LastPageVisited.POPULARS){
			text = "Most Popular Salads";
		}
		else 
			if (option == LastPageVisited.DRINKS){
				text = "Drinks";
			} else {
				text = "Original Salads";
			}

		return text;
	}

	/**
	 * Retursn the products to show, according to the selection of the user
	 * @return
	 */
	private static List<Product> getProducts(){
		LastPageVisited option =  LastPageVisited.ORIGINALS;
		if (session.contains(LAST_PAGE_VISITED))
			option = (LastPageVisited)Enum.valueOf(LastPageVisited.class, session.get(LAST_PAGE_VISITED));

		if (option == LastPageVisited.POPULARS){
			TypedQuery<Product> query = GenericModel.em().createQuery("SELECT c FROM Salad c  WHERE name <> '"+DataManager.CUSTOM_SALAD_NAME+"' ORDER BY c.popularity DESC", Product.class);
			return query.getResultList();

			//return newList;
		}

		// Get Drinks
		if (option == LastPageVisited.DRINKS)
			return Drink.findAll();

		// Get Originals
		TypedQuery<Product> query = GenericModel.em().createQuery("SELECT c FROM Salad c WHERE creator is NULL AND name <> '"+DataManager.CUSTOM_SALAD_NAME+"'", Product.class);
		return query.getResultList();	
	}

	public static void populars(){
		session.put(LAST_PAGE_VISITED, LastPageVisited.POPULARS);
		index2();
	}

	public static void originals(){
		session.put(LAST_PAGE_VISITED, LastPageVisited.ORIGINALS);
		index2();		
	}

	public static void drinks(){
		session.put(LAST_PAGE_VISITED, LastPageVisited.DRINKS);
		index2();		
	}

	/**
	 * Retrieves the Shopping cart of the current user.
	 * @return The shopping cart
	 */
	public static ShoppingCart getShoppingCart() {
		// if user exists
		User user = getCurrentUser();
		if (user != null) {
			if (user.currentShoppingCart == null) {
				user.currentShoppingCart = new ShoppingCart(false);
				user.save();
			}		
			return user.currentShoppingCart;
		} else {
			// If shopping cart exists
			if (session.get(SHOPPING_CART_ID) != null) {
				Long shoppingCartId = Long.parseLong(session.get(SHOPPING_CART_ID));
				return ShoppingCart.findById(shoppingCartId);
			} 

			ShoppingCart shoppingCart = new ShoppingCart(false);
			session.put(SHOPPING_CART_ID, shoppingCart.id);
			return shoppingCart;		
		}
	}

	static User getCurrentUser() {
		if (session.get(USER_ID) != null) {
			Long id = Long.parseLong(session.get(USER_ID));
			return User.findById(id);
		}
		return null;
	}

	/**
	 * Adds a new product to the shopping cart
	 * @param productId The id of the product to add
	 */
	public static void addToCart(Long productId) {    	
		DataManager.addLineCart(getShoppingCart(), productId);
	}

	public static void deleteFromCart(Long lineId) {
		ShoppingCartLine line = ShoppingCartLine.findById(lineId);
		ShoppingCart shoppingCart = getShoppingCart();
		shoppingCart.decrementLine(line);
	}

	/**
	 * Renders the Shopping cart section
	 * @ If finding the shopping car throws an exception.
	 */
	public static void shoppingCartTotal() {
		ShoppingCart shoppingCart = getShoppingCart();
		User user = getCurrentUser();
		render(shoppingCart, user);
	}

	public static void nutritionDetails() {
		ShoppingCart shoppingCart = getShoppingCart();
		User user = getCurrentUser();
		render(shoppingCart, user);
	}

	public static void purchaseOrder(String firstName,
			String lastName, String street, String streetNo,
			String postalCode, String city, String phone, String email,
			String paymentMethod, String creditCardNo, String delivery, 
			boolean isMale) throws EmailException {
		validation.required(firstName);
		validation.required(lastName);
		DeliveryOption deliveryOption = getDeliveryOption(delivery);
		if (deliveryOption == DeliveryOption.DELIVERY) {
			validation.required(street);
			validation.required(streetNo);
			validation.required(postalCode);
			validation.required(city);
			// only numbers allowed
			validation.match(streetNo, "^[0-9]+$");
			validation.match(postalCode, "^[0-9]+$");
		}
		validation.required(phone);
		validation.required(email);
		validation.phone(phone);
		validation.email(email);
		PaymentMethod payment = getPaymentMethod(paymentMethod);
		if (payment == PaymentMethod.CREDIT_CARD) {
			validation.required(creditCardNo);
			validation.match(creditCardNo, "^[0-9]+$");
		}
		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			flash.put("purchaseError", true);
			index2();
		}
		User customer = getCurrentUser();
		if (customer == null)
			customer = new User(firstName, lastName, email);
		if (customer.shippingInformation == null) {
			customer.shippingInformation = new ShippingInformation(phone, street, streetNo, city, postalCode);
			customer.save();
		}
		ShoppingCart shoppingCart = getShoppingCart();
		PurchasedOrder order = new PurchasedOrder(customer, shoppingCart, 
				payment, deliveryOption);
		// Send a confirmation email for the order.
		sendConfirmationEmail(customer.email, order);
		// Show it in a floating window according to the mock-up.
		flash.put("purchaseSuccess", true);
		// Now the shopping cart should be empty because is billed.
		session.remove(SHOPPING_CART_ID);
		index2();
	}

	private static void sendConfirmationEmail(String userEmail, PurchasedOrder order) throws EmailException {
		SimpleEmail email = new SimpleEmail();
		email.setFrom("staff@mixyoursalad.com");
		email.addTo(userEmail);
		email.setSubject("Confirmation of your order");
		email.setMsg("Dear customer,\n\n" +
				"thank you for having used our service.\n" +
				"This is a confirmation e-mail that confirms your order.\n\n" +
				"TOTAL: $" + order.getPrice() + "\n\n" +
				"Kind Regards,\n" +
				"MixYourSalad Team");
		Mail.send(email); 
	}

	private static PaymentMethod getPaymentMethod(String paymentMethod) {
		if (paymentMethod.equals("cash"))
			return PaymentMethod.CASH;
		return PaymentMethod.CREDIT_CARD;
	}

	private static DeliveryOption getDeliveryOption(String delivery) {
		if (delivery.equals("delivery")) {
			return DeliveryOption.DELIVERY;
		}
		return DeliveryOption.PICK_UP;
	}

	public static void saveShippingInfo(String phone, String companyName,
			String street, String streetNo, String postalCode, String city){
		validation.required(phone);
		validation.required(street);
		validation.required(streetNo);
		validation.required(postalCode);
		validation.required(city);
		validation.phone(phone);
		// the length of the postalCode needs to be 5 and there are only numbers allowed
		validation.minSize(postalCode, 5);
		validation.maxSize(postalCode,  5);
		validation.match(postalCode, "^[0-9]+$");

		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			index2();
		}

		ShippingInformation shippingInfo = new ShippingInformation(phone, companyName, street, streetNo, city, postalCode);
		User user = getCurrentUser();
		user.shippingInformation = shippingInfo;
		user.save();
		flash.put(postalCode, "Invalid postal code");
		index2();
	}

	public static void saveCalories(String targetCalories){
		DietInformation dietInfo;

		validation.required(targetCalories);
		validation.match(targetCalories, "^[0-9]+$");
		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			index2();
		}
		dietInfo = new DietInformation(Integer.parseInt(targetCalories));
		User user = getCurrentUser();
		user.dietInformation = dietInfo;
		user.save();
		index2();
	}

	public static void saveDietInfo(String age, String height, String weight, boolean isMale, String sportProfile){
		DietInformation dietInfo;

		validation.required(age);
		//only numbers allowed
		validation.match(age, "^[0-9]+$");
		// TODO validate the parameters correctly as float numbers			
		validation.required(height);
		validation.match(height, "^[0-9]+$");
		validation.required(weight);
		validation.match(weight, "^[0-9]+$");
		validation.required(isMale);
		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			index2();
		}
		double profile = SportProfile.getProfile(sportProfile);
		dietInfo = new DietInformation(Integer.parseInt(age), Integer.parseInt(height), Integer.parseInt(weight), isMale, profile);

		User user = getCurrentUser();
		user.dietInformation = dietInfo;
		user.save();
		index2();
	}

	public static void saveUserInfo(String firstName, String lastName, String email){
		validation.required(firstName);
		validation.required(lastName);
		validation.required(email);

		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			index2();
		}

		User user = getCurrentUser();
		user.firstName = firstName;
		user.lastName = lastName;
		user.email = email;
		user.save();
		index2();
	}
	/**
	 * Gets the image in a blob. Not functional yet.
	 * @param id
	 */
	public static void getImage(Long id) {
		Product product = Product.findById(id);        
		renderBinary(product.icon.get());
	}

	public static void aboutus(){
		render();
	}

	public static void privacy(){
		render();
	}

	public static void contactus(){
		render();
	}

	public static void faq(){
		render();
	}

	public static OAuth2 FACEBOOK = new OAuth2(
			"https://graph.facebook.com/oauth/authorize",
			"https://graph.facebook.com/oauth/access_token",
			"323482344450045",
			"beec3b45abc5b4937f5bd20b9bc40d9c"
			);

	public static void loginView() {
		User u = getCurrentUser();
		render(u);
	}

	public static void auth() {
		if (OAuth2.isCodeResponse()) {
			User user = new User("a", "a", "a@a.com");
			OAuth2.Response response = FACEBOOK.retrieveAccessToken(authURL());
			user.access_token = response.accessToken;
			JsonObject me = null;
			// if user was authenticated
			if (user.access_token != null) {
				me = WS.url("https://graph.facebook.com/me?access_token=%s", WS.encode(user.access_token)).get().getJson().getAsJsonObject();
			}

			if (me != null) {
				JsonElement emailObj = me.get("email");
				String email = "";
				if (emailObj != null) {
					email = emailObj.getAsString();
				}
				User existingUser = User.find("byEmail", email).first();
				if (existingUser == null) {
					user.firstName = me.get("first_name").getAsString();
					user.lastName = me.get("last_name").getAsString();
					user.email = email;
					user.save();
					session.put(USER_ID, user.id);
				} else {
					session.put(USER_ID, existingUser.id);
				}
			}
			index2();
		}
		FACEBOOK.retrieveVerificationCode(authURL());
	}

	public static void loginUser(String loginEmail, String loginPassword) {

		validation.required(loginEmail);
		validation.required(loginPassword);
		validation.email(loginEmail);
		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			flash.put("loginUserError", true);
			index2();
		}

		JPAQuery query = User.find("byEmail", loginEmail);
		User user = query.first();
		if(user!=null) {
			PasswordUtility pwdUtility = PasswordUtility.getInstance();
			String encryptedPassword = pwdUtility.encrypt(loginPassword);
			boolean isCorrect = user.checkPassword(encryptedPassword);
			if(isCorrect) {
				session.put(USER_ID, user.id);
				index2();
			}
		}
		String errorMsg = "Email or Password are not correct.";
		flash.put("emailOrPasswordError", errorMsg);
		flash.put("loginUserError", true);
		index2();
	}

	public static void registerNewUser(String firstName, String lastName,
			String registerEmail, String registerPassword, String confirmPassword) {
		validation.required(firstName);
		validation.required(lastName);
		validation.required(registerEmail);
		validation.required(registerPassword);
		validation.required(confirmPassword);

		validation.email(registerEmail);
		validation.equals(confirmPassword, registerPassword);

		if (validation.hasErrors()) {
			params.flash();
			validation.keep();
			flash.put("registerUserError", true);
			index2();
		}
		PasswordUtility pwdUtility = PasswordUtility.getInstance();
		String encryptedPassword = pwdUtility.encrypt(registerPassword);
		User user = new User(firstName, lastName, registerEmail, encryptedPassword);
		user.save();
		index2();

	}

	public static void logoutUser() {
		session.remove(USER_ID);
		session.remove(SHOPPING_CART_ID);
		index2();
	}

	static String authURL() {
		return play.mvc.Router.getFullUrl("Application.auth");
	}

}